#include <buola/scene/cmesh.h>
#include <buola/scene/cvertexdata.h>
#include <buola/scene/cindexdata.h>
#include <buola/scene/cbone.h>
#include <buola/gl.h>
#include <buola/image/io.h>
#include <buola/algorithm/comparison.h>

#include "import/cobjparser.h"
#include "import/civparser.h"
#include "import/cstlparser.h"

namespace buola { namespace scene {

CMesh::CMesh()
{
}

CMesh::~CMesh()
{
}

void CMesh::AddSubMesh(const std::string &pName,EPrimitive pPrimitive,int pFirstIndex,int pIndexCount)
{
    SSubMesh lSubMesh;

    lSubMesh.mName=pName;
    lSubMesh.mPrimitive=pPrimitive;
    lSubMesh.mFirstIndex=pFirstIndex;
    lSubMesh.mIndexCount=pIndexCount;
    
    mSubMeshes.push_back(lSubMesh);
}

void CMesh::Render(const std::string &pSubMesh)
{
    msg_info() << "should render submesh\n";
/*   
    glBindBuffer(GL_ARRAY_BUFFER,mVertexData.GetVBO());
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(2);
    glEnableVertexAttribArray(3);
    glEnableVertexAttribArray(4);
    glEnableVertexAttribArray(5);
    glVertexAttribPointer(0,3,GL_FLOAT,false,80,BUFFER_OFFSET(0));
    glVertexAttribPointer(2,3,GL_FLOAT,false,80,BUFFER_OFFSET(12));
    glVertexAttribPointer(3,2,GL_FLOAT,false,80,BUFFER_OFFSET(24));
    glVertexAttribIPointer(4,4,GL_UNSIGNED_INT,80,BUFFER_OFFSET(32));
    glVertexAttribPointer(5,4,GL_FLOAT,false,80,BUFFER_OFFSET(48));

    if(!mMaterial.IsEmpty())
    {
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1,4,GL_FLOAT,false,80,BUFFER_OFFSET(64));
    }
    if(!mTexture.IsNull())
    {
        mTexture.Bind();
    }
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,mIndexData->GetIBO());

    glPrimitiveRestartIndex(16777216);
    glEnable(GL_PRIMITIVE_RESTART);

    for(int i=0;i<mSubMeshes.size();i++)
    {
        SSubMesh &lSubMesh=*mSubMeshes[i];
        if(lSubMesh.mName!=pSubMesh) continue;

        glDrawElements(GL_TRIANGLE_FAN,lSubMesh.mIndexCount,GL_UNSIGNED_INT,BUFFER_OFFSET(lSubMesh.mFirstIndex));
    }

    glDisable(GL_PRIMITIVE_RESTART);

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(2);
    glDisableVertexAttribArray(3);
    glDisableVertexAttribArray(4);
    glDisableVertexAttribArray(5);
    glFinish();
*/
}

void CMesh::Render()
{
//    glActiveTexture(GL_TEXTURE0);
    
    glBindBuffer(GL_ARRAY_BUFFER,mVertexData.GetVBO());
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(2);
    glEnableVertexAttribArray(3);
//    glEnableVertexAttribArray(4);
//    glEnableVertexAttribArray(5);
    mVertexData.SetVertexAttrib(0,FIELD_POSITION);
    mVertexData.SetVertexAttrib(2,FIELD_NORMAL);
    mVertexData.SetVertexAttrib(3,FIELD_TEXCOORD);
//    glVertexAttribPointer(0,3,GL_FLOAT,false,80,BUFFER_OFFSET(0));
//    glVertexAttribPointer(2,3,GL_FLOAT,false,80,BUFFER_OFFSET(12));
//    glVertexAttribPointer(3,2,GL_FLOAT,false,80,BUFFER_OFFSET(24));
//    glVertexAttribIPointer(4,4,GL_UNSIGNED_INT,80,BUFFER_OFFSET(32));
//    glVertexAttribPointer(5,4,GL_FLOAT,false,80,BUFFER_OFFSET(48));

//    if(!mMaterial.IsEmpty())
//    {
//        glEnableVertexAttribArray(1);
//        glVertexAttribPointer(1,4,GL_FLOAT,false,80,BUFFER_OFFSET(64));
//    }
    
//    if(!mTexture.IsNull())
//    {
//        mTexture.Bind();
//    }
    
    glEnable(GL_PRIMITIVE_RESTART);
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,mIndexData.GetIBO());

    GLenum lPrimitive=GL_TRIANGLE_STRIP;
    if(!mSubMeshes.empty())
    {
        switch(mSubMeshes[0].mPrimitive)
        {
        case EPrimitive::TRIANGLE_FAN:
            lPrimitive=GL_TRIANGLE_FAN;
            break;
        case EPrimitive::TRIANGLES:
            lPrimitive=GL_TRIANGLES;
            break;
        case EPrimitive::TRIANGLE_STRIP:
        default:
            lPrimitive=GL_TRIANGLE_STRIP;
        }
    }
    
    glDrawElements(lPrimitive,mIndexData.GetIndexCount(),GL_UNSIGNED_INT,nullptr);
        
    glDisable(GL_PRIMITIVE_RESTART);

    glDisableVertexAttribArray(0);
//    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(2);
    glDisableVertexAttribArray(3);
//    glDisableVertexAttribArray(4);
//    glDisableVertexAttribArray(5);
}

void CMesh::CollectPolygons(std::vector<mat::CVec3d> &pPoints,std::vector<mat::CVec3d> &pNormals,
                            std::vector<int> &pFaces,const mat::CMat34d &pTransform)
{
    struct ATTRIBUTE_PACKED LVertex
    {
        float x,y,z;
        float xn,yn,zn;
        float u,v;
        uint32_t mIndices[4];
        float mWeights[4];
        float r,g,b,a;
    };

    LVertex *lVertex=mVertexData.Map<LVertex>(GL_WRITE_ONLY);
    GLuint *lIndices=mIndexData.Map(GL_READ_ONLY);
    GLuint lRestartIndex=mIndexData.GetPrimitiveRestartIndex();
    
    int lBegin=pPoints.size();
    for(int i=0;i<mIndexData.GetIndexCount();i++)
    {
        int lIndex=lIndices[i];
        if(lIndex==lRestartIndex)
        {
            pFaces.push_back(pPoints.size()-lBegin);
            lBegin=pPoints.size();
        }
        else
        {
            pPoints.push_back(pTransform*mat::CVec3d(lVertex[lIndex].x,lVertex[lIndex].y,lVertex[lIndex].z));
            pNormals.push_back(free_vector_product(pTransform,mat::CVec3d(lVertex[lIndex].xn,lVertex[lIndex].yn,lVertex[lIndex].zn)));
        }
    }
    
    if(lBegin!=pPoints.size())
    {
        pFaces.push_back(pPoints.size()-lBegin);
    }
    
    mVertexData.Unmap();
    mIndexData.Unmap();
}

PMesh CMesh::Import(const io::CURI &pURI,double pScale)
{
    std::string lExtension=pURI.Extension();

    if(iequals(lExtension,"obj"))
    {
        import::COBJParser lOBJParser;
        return lOBJParser.Parse(pURI,pScale);
        
    }
    else if(iequals(lExtension,"iv"))
    {
        import::CIVParser lIVParser;
        return lIVParser.Parse(pURI,pScale);
    }
    else if(iequals(lExtension,"stl"))
    {
        import::CSTLParser lSTLParser;
        return lSTLParser.Parse(pURI,pScale);
    }
    else
    {
        throw XArgument("extension not recognized in CMesh::Import");
    }
}

/*namespace gl*/ } /*namespace buola*/ }
